use std::fmt::Display;

use pest::Parser as _;
use pest::iterators::Pair;
use pest_derive::Parser;
use tracing::{debug, trace};

#[derive(Parser)]
#[grammar = "lexer.pest"]
pub struct SysYLexer;

#[derive(Debug)]
pub struct Token {
    pub token_type: TokenType,
    pub text: String,
    pub line: usize,
}

#[allow(unused)]
pub fn file_tokenize(input: &str) {
    let tokens: Vec<Token> = tokenize(input);
    let mut errors = Vec::new();
    let mut success = Vec::new();
    for token in tokens {
        match token.token_type == TokenType::ErrorToken {
            true => errors.push(token),
            false => success.push(token),
        }
    }

    if !errors.is_empty() {
        errors.iter().for_each(|token| {
            eprintln!("{token}");
        });
    } else {
        success.iter().for_each(|token| {
            eprintln!("{token}");
        });
    }
}

#[tracing::instrument(skip_all)]
pub fn tokenize(input: &str) -> Vec<Token> {
    let Ok(mut pairs) = SysYLexer::parse(Rule::program, input) else {
        unreachable!("不存在解析失败, 因为有error_token托底, 详见src/lexer.pest:67-69")
    };
    let mut tokens = Vec::new();
    if let Some(program_pair) = pairs.next() {
        // crate::write_to_file_for_log(&program_pair, "program_pair"); // 用于调试
        for token_pair in program_pair.into_inner() {
            if token_pair.as_rule() == Rule::EOI {
                break;
            }
            #[allow(clippy::unwrap_used)]
            // !safe: 有一层token包在外部
            // 提取其内部的第一个pair
            let actual_pair = token_pair.into_inner().next().unwrap();
            let (token_type, text) = get_token_type_and_text(&actual_pair);
            let token = Token {
                token_type,
                text,
                line: actual_pair.line_col().0,
            };
            trace!("成功解析token:\n{token:?}");
            tokens.push(token);
        }
    }
    tokens
}

#[tracing::instrument(skip_all)]
pub fn parse_integer_const(pair: &Pair<Rule>) -> String {
    match pair.as_rule() {
        #[allow(clippy::unwrap_used)]
        // !safe: 前端解析已经保证是有效的16进制数
        Rule::HEX_INT => u32::from_str_radix(
            pair.as_str()
                .trim_start_matches("0x")
                .trim_start_matches("0X"),
            16,
        )
        .unwrap()
        .to_string(),
        Rule::OCT_INT => u32::from_str_radix(pair.as_str().trim_start_matches("0"), 8)
            .unwrap_or(0)
            .to_string(),
        Rule::DEC_INT => pair.as_str().to_string(),
        _ => unreachable!(),
    }
}

#[tracing::instrument(skip_all)]
pub fn get_token_type_and_text(pair: &Pair<Rule>) -> (TokenType, String) {
    trace!("正在解析的pair:\n{pair:#?}");
    match pair.as_rule() {
        /* 关键字 */
        Rule::CONST => (TokenType::Const, pair.as_str().to_string()),
        Rule::INT => (TokenType::Int, pair.as_str().to_string()),
        Rule::VOID => (TokenType::Void, pair.as_str().to_string()),
        Rule::IF => (TokenType::If, pair.as_str().to_string()),
        Rule::ELSE => (TokenType::Else, pair.as_str().to_string()),
        Rule::WHILE => (TokenType::While, pair.as_str().to_string()),
        Rule::BREAK => (TokenType::Break, pair.as_str().to_string()),
        Rule::CONTINUE => (TokenType::Continue, pair.as_str().to_string()),
        Rule::RETURN => (TokenType::Return, pair.as_str().to_string()),
        /* 操作符 */
        Rule::EQ => (TokenType::Eq, pair.as_str().to_string()),
        Rule::NEQ => (TokenType::Neq, pair.as_str().to_string()),
        Rule::LE => (TokenType::Le, pair.as_str().to_string()),
        Rule::GE => (TokenType::Ge, pair.as_str().to_string()),
        Rule::AND => (TokenType::And, pair.as_str().to_string()),
        Rule::OR => (TokenType::Or, pair.as_str().to_string()),
        Rule::PLUS => (TokenType::Plus, pair.as_str().to_string()),
        Rule::MINUS => (TokenType::Minus, pair.as_str().to_string()),
        Rule::MUL => (TokenType::Mul, pair.as_str().to_string()),
        Rule::DIV => (TokenType::Div, pair.as_str().to_string()),
        Rule::MOD => (TokenType::Mod, pair.as_str().to_string()),
        Rule::ASSIGN => (TokenType::Assign, pair.as_str().to_string()),
        Rule::LT => (TokenType::Lt, pair.as_str().to_string()),
        Rule::GT => (TokenType::Gt, pair.as_str().to_string()),
        Rule::NOT => (TokenType::Not, pair.as_str().to_string()),
        /* 分隔符 */
        Rule::L_PAREN => (TokenType::LParen, pair.as_str().to_string()),
        Rule::R_PAREN => (TokenType::RParen, pair.as_str().to_string()),
        Rule::L_BRACE => (TokenType::LBrace, pair.as_str().to_string()),
        Rule::R_BRACE => (TokenType::RBrace, pair.as_str().to_string()),
        Rule::L_BRACKT => (TokenType::LBrackt, pair.as_str().to_string()),
        Rule::R_BRACKT => (TokenType::RBrackt, pair.as_str().to_string()),
        Rule::COMMA => (TokenType::Comma, pair.as_str().to_string()),
        Rule::SEMICOLON => (TokenType::Semicolon, pair.as_str().to_string()),
        /* 数值 */
        Rule::HEX_INT => (TokenType::IntegerConst, parse_integer_const(pair)),
        Rule::OCT_INT => (TokenType::IntegerConst, parse_integer_const(pair)),
        Rule::DEC_INT => (TokenType::IntegerConst, parse_integer_const(pair)),
        /* 标识符 */
        Rule::ident => (TokenType::Ident, pair.as_str().to_string()),
        /* 错误处理 */
        Rule::error_token => (TokenType::ErrorToken, pair.as_str().to_string()),
        _ => unreachable!("解析出了未知的token"),
    }
}

impl Display for Token {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self.token_type == TokenType::ErrorToken {
            true => write!(
                f,
                "{} type A at Line {}: Mysterious character `{}`.",
                self.token_type, self.line, self.text
            ),
            false => write!(
                f,
                "{} {} at Line {}.",
                self.token_type, self.text, self.line
            ),
        }
    }
}

#[derive(Debug, PartialEq)]
pub enum TokenType {
    // 关键字
    Const,
    Int,
    Void,
    If,
    Else,
    While,
    Break,
    Continue,
    Return,
    // 运算符
    Plus,
    Minus,
    Mul,
    Div,
    Mod,
    Assign,
    Eq,
    Neq,
    Lt,
    Gt,
    Le,
    Ge,
    Not,
    And,
    Or,
    // 分隔符
    LParen,
    RParen,
    LBrace,
    RBrace,
    LBrackt,
    RBrackt,
    Comma,
    Semicolon,
    // 数值
    IntegerConst,
    // 标识符
    Ident,
    // 错误处理
    ErrorToken,
}

impl std::fmt::Display for TokenType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self {
                TokenType::Const => "CONST",
                TokenType::Int => "INT",
                TokenType::Void => "VOID",
                TokenType::If => "IF",
                TokenType::Else => "ELSE",
                TokenType::While => "WHILE",
                TokenType::Break => "BREAK",
                TokenType::Continue => "CONTINUE",
                TokenType::Return => "RETURN",
                TokenType::Plus => "PLUS",
                TokenType::Minus => "MINUS",
                TokenType::Mul => "MUL",
                TokenType::Div => "DIV",
                TokenType::Mod => "MOD",
                TokenType::Assign => "ASSIGN",
                TokenType::Eq => "EQ",
                TokenType::Neq => "NEQ",
                TokenType::Lt => "LT",
                TokenType::Gt => "GT",
                TokenType::Le => "LE",
                TokenType::Ge => "GE",
                TokenType::Not => "NOT",
                TokenType::And => "AND",
                TokenType::Or => "OR",
                TokenType::LParen => "L_PAREN",
                TokenType::RParen => "R_PAREN",
                TokenType::LBrace => "L_BRACE",
                TokenType::RBrace => "R_BRACE",
                TokenType::LBrackt => "L_BRACKT",
                TokenType::RBrackt => "R_BRACKT",
                TokenType::Comma => "COMMA",
                TokenType::Semicolon => "SEMICOLON",
                TokenType::Ident => "IDENT",
                TokenType::IntegerConst => "INTEGER_CONST",
                TokenType::ErrorToken => "Error",
            }
        )
    }
}
